<template>
  <div class="home">
    <Layout class="content">
      <Sider hide-trigger class="content">
        <Card style="width:100%; min-height: 500px" dis-hover>
          <template #title>
            <SourceSelect @changeValue="handlerChangeValue($event)"/>
          </template>
          <DataStructureLazyTree v-if="applySource"
                                 :id="applySource">
          </DataStructureLazyTree>
        </Card>
      </Sider>
      <Layout class="content" :style="{padding: '0 12px 12px'}">
        <Content>
          <Card style="width:100%" dis-hover>
            <template #title>
              <Space>
                <Button type="primary" size="small" :loading="tableLoading" :disabled="!applySource || !activeEditorValue"
                        icon="md-arrow-dropright-circle" @click="handlerRun()">
                  {{ $t('common.run') }}
                </Button>
                <Button type="dashed" size="small" :disabled="!applySource || !activeEditorValue" icon="md-code" @click="handlerFormat()">
                  {{ $t('common.format') }}
                </Button>
                <Button type="error" size="small" :disabled="!applySource || !tableLoading"
                        icon="md-close-circle" @click="handlerCancel()">
                  {{ $t('common.cancel') }}
                </Button>
                <Button v-if="tableConfigure" type="primary" size="small" icon="md-add" @click="handlerCreateSnippet()">
                  {{ $t('common.snippet') }}
                </Button>
                <Poptip v-if="response.data" placement="bottom">
                  <Button icon="md-clock" size="small">
                    {{ response.data.processor.elapsed }} ms
                  </Button>
                  <template #content>
                    <Space :size="20">
                      <template #split> |</template>
                      <NumberInfo class="center" :total="response.data.connection.elapsed">
                        <template #title>
                          <span>Connection</span>
                          <Tooltip content="Connection time!" placement="bottom">
                            <Icon type="md-help-circle" style="margin-left: 3px"/>
                          </Tooltip>
                        </template>
                      </NumberInfo>
                      <NumberInfo class="center" :total="response.data.processor.elapsed">
                        <template #title>
                          <span>Execute</span>
                          <Tooltip content="Execute time!" placement="bottom">
                            <Icon type="md-help-circle" style="margin-left: 3px"/>
                          </Tooltip>
                        </template>
                      </NumberInfo>
                    </Space>
                  </template>
                </Poptip>
                <Badge v-if="applySource && activeEditorValue" :count="aiSupportType.length">
                  <Button type="primary" size="small" icon="md-ionitron" @click="handlerVisibleHelp(true)"></Button>
                </Badge>
              </Space>
            </template>
            <div ref="editorContainer">
              <Tabs v-model="activeKey" type="card" :animated="false" @on-tab-remove="handlerMinusEditor" @on-click="handlerChangeEditor">
                <template #extra>
                  <Button @click="handlerPlusEditor" size="small" type="primary" icon="md-add"/>
                </template>
                <TabPane v-for="editor in editors" :key="editor.key" :name="editor.key" :label="editor.title" :closable="editor.closable">
                </TabPane>
                <MonacoEditor theme="vs" :options="{theme: 'vs-dark', fontSize: 15}" language="sql" :height="300"
                              :key="activeKey.value" @change="handlerChangeEditorValue" :width="'100%'"
                              v-model:value="activeEditorValue" @editorDidMount="handlerEditorDidMount($event, 'mysql')">
                </MonacoEditor>
              </Tabs>
            </div>
          </Card>
          <div style="margin-top: 5px;">
            <BasicTableComponent v-if="tableConfigure" :configure="tableConfigure"></BasicTableComponent>
          </div>
        </Content>
      </Layout>
    </Layout>
    <SnippetDetails v-if="snippetDetails" :isVisible="snippetDetails"
                    :codeSnippet="activeEditorValue" @close="handlerCloseSnippetDetails($event)">
    </SnippetDetails>
    <QueryAiHelp v-if="visibleAiHelp" :isVisible="visibleAiHelp" :content="activeEditorValue"
                 :aiSupports="aiSupportType" :error="error"
                 :engine="engine" @close="handlerVisibleHelp($event)"></QueryAiHelp>
  </div>
</template>

<script lang="ts">
import {ExecuteModel} from "@/model/ExecuteModel";
import {ExecuteService} from "@/services/ExecuteService";
import {FormatService} from "@/services/FormatService";
import axios, {CancelTokenSource} from "axios";
import * as monaco from 'monaco-editor';
import MonacoEditor from 'monaco-editor-vue3';
import {defineComponent, ref} from "vue";
import {useRouter} from "vue-router";
import {TableConfigure} from "@/components/table/TableConfigure";
import SourceSelect from "@/components/source/SourceSelect.vue";
import SnippetDetails from "@/views/admin/snippet/SnippetDetails.vue";
import BasicTableComponent from "@/components/table/BasicTable.vue";
import {AuditService} from "@/services/AuditService";
import FunctionsService from "@/services/settings/functions/FunctionsService";
import {useI18n} from "vue-i18n";
import QueryAiHelp from "@/views/user/console/QueryAiHelp.vue";
import {HttpCommon} from "@/common/HttpCommon";
import SnippetService from "@/services/SnippetService";
import UserService from "@/services/UserService";
import {join} from "lodash";
import DataStructureLazyTree from "@/components/common/DataStructureLazyTree.vue";

const editors = ref<{ title: string; key: string; closable?: boolean }[]>([
  {title: 'Editor', key: '1', closable: false}
]);
const activeKey = ref(editors.value[0].key);
const editorMap = new Map<string, monaco.editor.ICodeEditor>();
const editorValueMap = new Map<string, string>();

export default defineComponent({
  name: "ConsoleIndex",
  components: {DataStructureLazyTree, QueryAiHelp, BasicTableComponent, SnippetDetails, SourceSelect, MonacoEditor},
  unmounted()
  {
    if (this.editorCompletionProvider) {
      this.editorCompletionProvider.dispose();
    }
  },
  setup()
  {
    const i18n = useI18n();
    return {
      i18n
    }
  },
  data()
  {
    return {
      applySource: 0,
      applySourceType: '',
      tableConfigure: null as TableConfigure,
      tableOptions: {},
      tableColumns: [],
      tableLoading: false,
      cancelToken: {} as CancelTokenSource,
      response: {},
      editorCompletionProvider: {} as monaco.IDisposable,
      snippetDetails: false,
      activeEditorValue: '',
      editors,
      activeKey,
      editorValueMap,
      visibleAiHelp: false,
      engine: null,
      aiSupportType: ['ANALYSIS', 'OPTIMIZE'],
      error: null
    }
  },
  created()
  {
    this.handlerInitialize();
  },
  mounted()
  {
    window.onresize = () => {
      if (editorMap.values().next().value) {
        editorMap.values().next().value.layout({width: this.$refs.editorContainer.offsetWidth, height: 300})
      }
    }
  },
  methods: {
    handlerInitialize()
    {
      const router = useRouter();
      if (router.currentRoute?.value?.query) {
        const id = router.currentRoute.value.query.id as unknown as number;
        const from = router.currentRoute.value.query.from;
        if (id && from) {
          if (from === 'snippet') {
            SnippetService.getById(id)
              .then((response) => {
                if (response.status && response.data?.code) {
                  this.activeEditorValue = response.data.code;
                }
              });
          }
          else if (from === 'history') {
            new AuditService().getById(id)
              .then((response) => {
                if (response.status && response.data?.content) {
                  this.activeEditorValue = response.data.content;
                  this.handlerChangeValue(response.data.plugin.id + ':' + response.data.plugin.type);
                }
              });
          }
        }
      }
    },
    handlerEditorDidMount(editor: any, language: string, newEditor?: string)
    {
      try {
        this.editorCompletionProvider.dispose();
      }
      catch (e) {
        console.log(e);
      }

      if (newEditor) {
        editorMap.set(newEditor, editor);
        editorValueMap.set(activeKey.value, '');
      }
      else {
        editorMap.set(activeKey.value, editor);
      }
      const client = new HttpCommon().getAxios();
      client.all([FunctionsService.getByPlugin(language), SnippetService.getSnippets(0, 100000), UserService.getSugs(this.applySource)])
        .then(client.spread((pluginResponse, snippetResponse, sugsResponse) => {
          const languageSugs = [];
          if (pluginResponse.status) {
            pluginResponse.data.content.forEach(value => {
              let kind = monaco.languages.CompletionItemKind.Text;
              switch (value.type.toLowerCase()) {
                case 'keyword':
                  kind = monaco.languages.CompletionItemKind.Keyword;
                  break
                case 'function':
                  kind = monaco.languages.CompletionItemKind.Function;
                  break
                case 'operator':
                  kind = monaco.languages.CompletionItemKind.Operator;
                  break
              }
              languageSugs.push({
                label: value.name,
                detail: this.i18n.t('common.' + value.type.toLowerCase()),
                kind: kind,
                documentation: this.i18n.t('common.description') + ':\n' + value.description + '\n\n' + this.i18n.t('common.example') + ':\n' + value.example,
                insertText: value.content
              });
            });
          }
          if (snippetResponse.status) {
            snippetResponse.data?.content.forEach(value => {
              let kind = monaco.languages.CompletionItemKind.Snippet;
              languageSugs.push({
                label: value.name,
                detail: this.i18n.t('common.snippet'),
                kind: kind,
                documentation: this.i18n.t('common.description') + ':\n' + value.description + '\n\n' + this.i18n.t('common.example') + ':\n' + value.code,
                insertText: value.code
              });
            });
          }
          if (sugsResponse.status) {
            const databaseSet = new Set();
            const tableSet = new Set();
            const columnSet = new Set();
            sugsResponse.data.forEach(value => {
              const array = value.split('.');
              // Build database prompt
              if (!databaseSet.has(array[0])) {
                databaseSet.add(array[0]);
                const database = monaco.languages.CompletionItemKind.Class;
                languageSugs.push({
                  label: array[0],
                  detail: this.i18n.t('common.database'),
                  kind: database,
                  documentation: this.i18n.t('common.description') + ':\n' + array[0],
                  insertText: array[0]
                });
              }
              // Build table prompt
              const tableName = join([array[0], array[1]], ".");
              if (!tableSet.has(tableName)) {
                tableSet.add(tableName);
                const table = monaco.languages.CompletionItemKind.Method;
                languageSugs.push({
                  label: tableName,
                  detail: this.i18n.t('common.table'),
                  kind: table,
                  documentation: this.i18n.t('common.description') + ':\n\t' + this.i18n.t('common.database') + ': ' + array[0]
                    + '\n\t' + this.i18n.t('common.table') + ': ' + array[1],
                  insertText: tableName
                });
              }
              // Build column prompt
              if (!columnSet.has(value)) {
                columnSet.add(value);
                const column = monaco.languages.CompletionItemKind.Field;
                languageSugs.push({
                  label: value,
                  detail: this.i18n.t('common.column'),
                  kind: column,
                  documentation: this.i18n.t('common.description') + ':\n\t' + this.i18n.t('common.database') + ': ' + array[0]
                    + '\n\t' + this.i18n.t('common.table') + ': ' + array[1]
                    + '\n\t' + this.i18n.t('common.column') + ': ' + array[2],
                  insertText: value
                });
              }
            });
          }
          this.editorCompletionProvider = monaco.languages.registerCompletionItemProvider("sql", {
            provideCompletionItems(): any
            {
              return {
                suggestions: languageSugs.map((item) => ({
                  ...item
                }))
              };
            },
            triggerCharacters: ['.']
          });
        }));
      setTimeout(() => {
        editorMap.values().next().value?.layout({width: this.$refs.editorContainer?.offsetWidth, height: 300})
      }, 200)
    },
    handlerRun()
    {
      this.tableConfigure = null;
      this.error = null;
      this.aiSupportType = ['ANALYSIS', 'OPTIMIZE'];
      this.response = {};
      this.cancelToken = axios.CancelToken.source();
      this.tableLoading = true;
      const editorContainer: HTMLElement = this.$refs.editorContainer as HTMLElement;
      const configure: ExecuteModel = {
        name: this.applySource,
        content: this.activeEditorValue,
        format: "JSON"
      };
      new ExecuteService()
        .execute(configure, this.cancelToken.token)
        .then((response) => {
          if (response.status) {
            this.response = response;
            const tConfigure: TableConfigure = {
              headers: response.data.headers,
              columns: response.data.columns,
              height: 340,
              width: editorContainer.offsetWidth + 20,
              showSeriesNumber: false
            };
            this.tableConfigure = tConfigure;
          }
          else {
            this.$Message.error({
              content: response.message,
              duration: 5,
              closable: true
            });
            this.error = response.message;
            this.aiSupportType.push('FIXEDBUGS');
            this.tableConfigure = null;
          }
        })
        .finally(() => {
          this.tableLoading = false;
        });
    },
    handlerChangeValue(value: string)
    {
      const idAndType = value.split(':');
      this.applySource = idAndType[0];
      this.applySourceType = idAndType[1];
      this.engine = idAndType[1];
      setTimeout(() => {
        if (this.editorCompletionProvider) {
          this.editorCompletionProvider.dispose();
        }
        this.handlerEditorDidMount(editorMap.get(activeKey.value), idAndType[1]);
      }, 200)
    },
    handlerFormat()
    {
      const configure = {
        sql: this.activeEditorValue
      };
      new FormatService()
        .formatSql(configure)
        .then((response) => {
          if (response.status) {
            this.activeEditorValue = response.data;
            editorValueMap.set(activeKey.value, this.activeEditorValue);
          }
          else {
            this.$Message.error(response.message);
          }
        });
    },
    handlerCancel()
    {
      this.cancelToken.cancel("Cancel query");
    },
    handlerCreateSnippet()
    {
      this.snippetDetails = true;
    },
    handlerCloseSnippetDetails(value: boolean)
    {
      this.snippetDetails = value;
    },
    handlerPlusEditor()
    {
      this.error = null;
      this.aiSupportType = ['ANALYSIS', 'OPTIMIZE'];
      activeKey.value = 'newTab' + activeKey.value + Date.parse(new Date().toString());
      editors.value.push({title: 'New Tab', key: activeKey.value, closable: true});
      editorValueMap.set(activeKey.value, '');
      this.handlerEditorDidMount(null, this.applySourceType, activeKey.value);
      this.activeEditorValue = editorValueMap.get(activeKey.value) as string;
    },
    handlerMinusEditor(targetKey: string)
    {
      let lastIndex = 0;
      editors.value.forEach((editor, i) => {
        if (editor.key === targetKey) {
          lastIndex = i - 1;
        }
      });
      editors.value = editors.value.filter(editor => editor.key !== targetKey);
      if (editors.value.length && activeKey.value === targetKey) {
        if (lastIndex >= 0) {
          activeKey.value = editors.value[lastIndex].key;
        }
        else {
          activeKey.value = editors.value[0].key;
        }
      }
      editorMap.delete(targetKey as string);
      editorValueMap.delete(targetKey as string);
      this.handlerChangeEditor(activeKey.value);
    },
    handlerChangeEditor(targetKey: string)
    {
      this.error = null;
      this.aiSupportType = ['ANALYSIS', 'OPTIMIZE'];
      this.activeEditorValue = editorValueMap.get(targetKey) as string;
    },
    handlerChangeEditorValue(value: string)
    {
      editorValueMap.set(activeKey.value, value);
    },
    handlerVisibleHelp(value: boolean)
    {
      this.visibleAiHelp = value;
    }
  },
  // Prevents errors from affecting other components
  errorCaptured(err: Error, vm)
  {
    return false;
  }
});
</script>
<style scoped>
.content {
  background-color: #FFFFFF;
}

.center {
  text-align: center;
}

@keyframes changDeg {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

.go {
  animation: changDeg 2s linear 0.2s infinite;
}
</style>
